/* License Notice:
**
** This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
** This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
**   GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
**   along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file arcade_fighter.hpp
 * @author TooOld2Rock'nRoll
 * @date 2022/12/15
 * @version 0.1.0
 * @brief Arcade Fighter Library main class.
 *
 * The Arcade Fighter Library is a studying project!<br>
 * It is NOT in any way or form a stable and robust commercial
 *    game engine. We may get there one day, but for now, it's simple not
 *    the objective of this project to offer a commercial solution.<br>
 * What we mean by a studying project is that it's meant for people studying
 *    game development in all it's forms. Our code tries to be easy to read
 *    and well documented as much as possible, all our sources for ideas and
 *    algorithms are stated on the class headers.<br>
 * Check the [README.md](../README.md) for more information.
 *
 * @see https://learnopengl.com/
 * @see https://gameprogrammingpatterns.com/
 *
 * @todo When upgrading to SDL3, we can use: https://wiki.libsdl.org/SDL3/SDL_SyncWindow instead of SDL_WaitEventTimeout at init().
 * @todo This is quickly reducing to a global setup/init method and the game loop, maybe separate everything and make specific??? Would it make easier when implementing menu screens, transitions, etc?
 * @todo Find a way to open the audio interface with custom values from config.
 * @todo is it necessary to change the shaders perspective when the window size changes? Add a callback to let user know and do something?
 * @todo program exit event shouldn't be hard coded, load from a default input file, change to a user defined when we implement auto map keys.
 * @todo setPlayer is doing nothing useful, this will probably change once we are saving/loading config from files.
 * @todo add a way to enable/disable fps count on screen/terminal (do this when text rendering is ready).
 * @todo implement all the Window events, like pause level when loose focus etc: https://wiki.libsdl.org/SDL2/SDL_WindowEventID
 */
#ifndef _ARCADE_FIGHTER_H_
#define _ARCADE_FIGHTER_H_

/*---- Includes ----*/
#include "graphics/window_manager.hpp"
#include "graphics/opengl.hpp"
#include "camera2D.hpp"

#include "audio/audio.hpp"

#include "event_manager.hpp"
#include "resource_manager.hpp"

#include "player.hpp"
#include "scenery.hpp"
#include "level.hpp"


/**
 * @brief Main Class for the ArcadeFighter Library.
 *
 * This is the class where most of the automation is done, a high level interface that abstracts all other classes and
 *    do as much of the work as possible without getting in your way and the actual game you are trying to implement.<br>
 * Some predefined configurations may be set in the config.h file, some options are to hardcode the library values and
 *    behavior, some are just safe "first boot" defaults that are supposed to be set more permanently by the player at
 *    some point.<br>
 * A instance of this class must be created before any other class from this library or other OpenGl related objects,
 *    it's unfortunate to have this limitation, but it's necessary to load the OpenGl APIs.<br>
 * The constructor will create a generic window and, if required, just grab the window manager to set very particular
 *    needs of your project.<br>
 * OpenGl itself will be initialized with common settings and generic values, like Black will be used to clear the
 *    screen on every frame, transparency will be set to Alpha channel and so forth. Feel free to change any setting
 *    to your project's needs using the usual OpenGl interfaces.<br>
 * This library comes with a set of very simple builtin shaders just use the default ResourceManager::getBasicShader (),
 *    but if you choose to use custom ones, it's expected they contain a model, projection and view mat4 uniforms on the
 *    vertex shader and a color vec3 uniform on the fragment shader for the various operations needed by the library.<br>
 * In case of panic, [Alt+F4] will be automatically handled as the system (GNU Linux) recommended method of hard exiting
 *    the program no matter the current state it is in at the time. It is recommended to also offer an option for the
 *    player to set their own preferred method.
 */
class ArcadeFighter
{
    public:
        /**
         * @brief Different Game Loop regimes depending on your game needs.
         *
         * Implemented based on <em>Gaffer On Games</em> article from 2004/Jun/10 titled "Fix Your Timestep!"
         *
         * @see https://gafferongames.com/post/fix_your_timestep/
         */
        enum delta_time_style_e
        {
            FIXED = 0,  ///< Try to keep a set FPS.
            VARIABLE,   ///< Runs as fast as it can all the time (most likely VSYNC bound!!).
            SEMI_FIXED, ///< Game logic iterates as fast it can, rendering tries to keep at a set FPS (Probably what you are looking for!!)
            CHUNKY,     ///< Game logic iterates at set time chunks, rendering is bound by set FPS.
            _max_delta_style ///< Just to keep track of how many possible variation we are handling.
        };//end delta_time_style

    private:
        /* system state */
        bool b_should_quit = false;

        /* physical display and graphics interface */
        WindowManager *p_sdl_instance = nullptr;
        OpenGl *p_gl_instance = nullptr;

        unsigned ui_display = 0; //which display to use

        /* Players data */
        Player *p_players[PLAYERS_COUNT];

        /* keep track of delta time on the main game loop */
        time_keep_s _main_delta_t;
        float f_target_delta_t = 0; //in sec

        time_keep_s _inner_delta_t;
        float f_target_step_t = 0; //in sec

        /* Local reference to the ResourceManager singleton. */
        ResourceManager *p_res_mgr_instance = nullptr;
        ResourceManager::global_status *p_lib_status = nullptr;
        //at this level, we will always use the global resource id

        /* Event handling */
        EventManager *p_ev_mgr_instance = nullptr;
        listenerID_t _event_listener_ID = 0;
        const EventManager::event_s *_hard_exit_command = nullptr;

        /* Audio interface */
        Audio *p_audio_instance = nullptr;

        /* Keep track of Level running */
        Level *_lv_running = nullptr;


    protected:
        void loop_section_events (Level &lv, double delta_t);
        void loop_section_logic (Level &lv, double delta_t);
        void loop_section_render (Level &lv, double delta_t);

        void run_fixedDeltaTime (Level &lv);
        void run_variableDeltaTime (Level &lv);
        void run_semiFixedDeltaTime (Level &lv);
        void run_chunkyDeltaTime (Level &lv);


    public:
        ArcadeFighter (const char *title = WINDOW_TITLE, unsigned display = 0, SDL_WindowFlags window_flags = (SDL_WindowFlags)0);
        ~ArcadeFighter ();

        void exit ();
        /** @brief Use in your main loop to check if program should terminate. */
        bool shouldQuit () const { return this->b_should_quit; }

        // To keep the game environment consistent and handle high level events
        void processEvents (double delta_t);
        //void update (double delta_t) {  }

        // Players
        void setPlayer (unsigned pl_number, Player *pl);

        // Level Management
        /** @brief Sets the target FPS for the Game Loop to run. */
        void setTargetFPS (unsigned fps) { this->f_target_delta_t = 1.f/fps; }
        /** @brief Sets the target FPS to bound the logic section of the Game Loop. */
        void setTargetGameLogicLoopFPS (unsigned state_fps) { this->f_target_step_t = 1.f/state_fps; }
        void levelStart (Level &lv, ArcadeFighter::delta_time_style_e style = GAME_LOOP_STYLE);
};//END ArcadeFighter

#endif //_ARCADE_FIGHTER_H_

